
#include "StdAfx.h"
#include <ISystem.h>
#include <ICryAnimation.h>
#include <IMovieSystem.h>
#include <IViewSystem.h>
#include "FlowBaseNode.h"

class CPlaySequence_Node : public CFlowBaseNode<eNCT_Instanced>, public IMovieListener
{
	enum INPUTS
	{
		EIP_Sequence = 0,
		EIP_Trigger,
		EIP_Stop,
		EIP_Precache,
		EIP_BreakOnStop,
		EIP_BlendPosSpeed,
		EIP_BlendRotSpeed,
		EIP_PerformBlendOut,
		EIP_StartTime,
		EIP_PlaySpeed,
		EIP_JumpToTime,
		EIP_TriggerJumpToTime,
		EIP_SequenceId,
	};

	enum OUTPUTS
	{
		EOP_Started = 0,
		EOP_Done,
		EOP_Finished,
		EOP_Aborted,
		EOP_SequenceTime,
		EOP_CurrentSpeed,
	};
	
	typedef enum
	{
		PS_NotStarted,
		PS_Playing,
		PS_Finished,
		PS_Last
	} EPlayingState;

	_smart_ptr<IAnimSequence> m_pSeq;
	SActivationInfo m_actInfo;
	EPlayingState m_playingState;
	float m_playSpeed;
	float m_endTime;

public:
	CPlaySequence_Node( SActivationInfo * pActInfo )
	{
		m_pSeq = 0;
		m_actInfo = *pActInfo;
		m_playingState = PS_NotStarted;
		m_playSpeed = 1.0f;
		m_endTime = 0;
	};

	virtual void GetMemoryUsage(ICrySizer * s) const
	{
		s->Add(*this);
	}

	~CPlaySequence_Node()
	{
		if (m_pSeq)
		{
			IMovieSystem *movieSys = gEnv->pMovieSystem;
			if (movieSys != 0)
				movieSys->RemoveMovieListener(m_pSeq, this);
		}
	};

	IFlowNodePtr Clone( SActivationInfo * pActInfo )
	{
		return new CPlaySequence_Node(pActInfo);
	}

	virtual void Serialize(SActivationInfo *pActInfo, TSerialize ser)
	{
		ser.BeginGroup("Local");
		if (ser.IsWriting()) 
		{
			ser.EnumValue("m_playingState", m_playingState,PS_NotStarted,PS_Last);
			float curTime = -1.0f;

			if(m_playingState == PS_Finished)
				curTime = m_endTime;
			else if (m_pSeq && gEnv->pMovieSystem)
			{
				curTime = gEnv->pMovieSystem->GetPlayingTime(m_pSeq);
				m_playSpeed = gEnv->pMovieSystem->GetPlayingSpeed(m_pSeq);
			}
			ser.Value("curTime", curTime);
			ser.Value("m_playSpeed", m_playSpeed);
		}
		else
		{
			EPlayingState playingState;
			float curTime = 0.0f;
			// for backward compatibility - TODO: remove
			bool playing = false;
			ser.Value("m_bPlaying", playing);
			playingState = playing ? PS_Playing : PS_NotStarted;
			/// end remove
			ser.EnumValue("m_playingState", playingState,PS_NotStarted,PS_Last);

			ser.Value("curTime", curTime);
			ser.Value("m_playSpeed", m_playSpeed);

			if (playingState == PS_Playing)
			{
				// restart sequence, possibly at the last frame
				StartSequence(pActInfo, curTime, false);
			} 
			else
			{
				// this unregisters only! because all sequences have been stopped already
				// by MovieSystem's Reset in GameSerialize.cpp
				StopSequence(pActInfo, true);
				m_playingState = PS_NotStarted;
			}
		}
		ser.EndGroup();
	}

	virtual void GetConfiguration( SFlowNodeConfig &config )
	{
		static const SInputPortConfig in_config[] = {
			InputPortConfig<string>( "seq_Sequence_File",_HELP("Name of the Sequence"), _HELP("Sequence") ),
			InputPortConfig_Void( "Trigger",_HELP("Starts the sequence"), _HELP("StartTrigger") ),
			InputPortConfig_Void( "Stop", _HELP("Stops the sequence"), _HELP("StopTrigger") ),
			InputPortConfig_Void( "Precache",_HELP("Precache keys that start in the first seconds of the animation. (Solves streaming issues)"), _HELP("PrecacheTrigger") ),
			InputPortConfig<bool>( "BreakOnStop", false, _HELP("If set to 'true', stopping the sequence doesn't jump to end.") ),
			InputPortConfig<float>("BlendPosSpeed", 0.0f, _HELP("Speed at which position gets blended into animation.") ),
			InputPortConfig<float>("BlendRotSpeed", 0.0f, _HELP("Speed at which rotation gets blended into animation.") ),
			InputPortConfig<bool>( "PerformBlendOut", false, _HELP("If set to 'true' the cutscene will blend out after it has finished to the new view (please reposition player when 'Started' happens).") ),
			InputPortConfig<float>("StartTime", 0.0f, _HELP("Start time from which the sequence'll begin playing.") ),
			InputPortConfig<float>("PlaySpeed", 1.0f, _HELP("Speed that this sequence plays at. 1.0 = normal speed, 0.5 = half speed, 2.0 = double speed.") ),
			InputPortConfig<float>("JumpToTime", 0.0f, _HELP("Jump to a specific time in the sequence.") ),
			InputPortConfig_Void("TriggerJumpToTime", _HELP("Trigger the animation to jump to 'JumpToTime' seconds.") ),
			InputPortConfig<int>("seqid_SequenceId", 0, _HELP("ID of the Sequence"), _HELP("SequenceId") ),
			InputPortConfig_Null()
		};
		static const SOutputPortConfig out_config[] = {
			OutputPortConfig_Void("Started", _HELP("Triggered when sequence is started") ),
			OutputPortConfig_Void("Done", _HELP("Triggered when sequence is stopped [either via StopTrigger or aborted via Code]"), _HELP("Done") ),
			OutputPortConfig_Void("Finished", _HELP("Triggered when sequence finished normally") ),
			OutputPortConfig_Void("Aborted", _HELP("Triggered when sequence is aborted (Stopped and BreakOnStop true or via Code)") ),
			OutputPortConfig_Void("SequenceTime", _HELP("Current time of the sequence") ),
			OutputPortConfig_Void("CurrentSpeed", _HELP("Speed at which the sequence is being played") ),
			OutputPortConfig_Null()
		};
		config.sDescription = _HELP( "Plays a Trackview Sequence" );
		config.pInputPorts = in_config;
		config.pOutputPorts = out_config;
		config.SetCategory(EFLN_APPROVED);
	}

	virtual void ProcessEvent( EFlowEvent event,SActivationInfo *pActInfo )
	{
		switch (event)
		{
			case eFE_Update:
			{
				ActivateOutput(pActInfo, EOP_CurrentSpeed, m_playSpeed);
				if(gEnv->pMovieSystem && m_pSeq)
				{
					float curTime = gEnv->pMovieSystem->GetPlayingTime(m_pSeq);
					ActivateOutput(pActInfo, EOP_SequenceTime, curTime);
				}
			}
			break;

			case eFE_Activate:
			{
				if (IsPortActive(pActInfo, EIP_Stop))
				{
					bool wasPlaying (m_playingState == PS_Playing);
					const bool bLeaveTime = GetPortBool(pActInfo, EIP_BreakOnStop);
				
					StopSequence(pActInfo, false, true, bLeaveTime);
					// we trigger manually, as we unregister before the callback happens

					if (wasPlaying)
					{
						ActivateOutput(pActInfo, EOP_Done, true); // signal we're done
						ActivateOutput(pActInfo, EOP_Aborted, true); // signal it's been aborted
						m_playingState = PS_Finished;
						NotifyEntities();
					}

				}
				if (IsPortActive(pActInfo, EIP_Trigger))
				{
					StartSequence(pActInfo, GetPortFloat(pActInfo, EIP_StartTime));
				}
				if (IsPortActive(pActInfo, EIP_Precache))
				{
					PrecacheSequence(pActInfo, GetPortFloat(pActInfo, EIP_StartTime));
				}
				if(IsPortActive(pActInfo, EIP_TriggerJumpToTime))
				{
					if(gEnv->pMovieSystem && m_pSeq)
					{
						float time = GetPortFloat(pActInfo, EIP_JumpToTime);
						time = clamp(time, m_pSeq->GetTimeRange().start, m_pSeq->GetTimeRange().end);
						gEnv->pMovieSystem->SetPlayingTime(m_pSeq, time);
					}
				}
				if(IsPortActive(pActInfo, EIP_PlaySpeed))
				{
					m_playSpeed = GetPortFloat(pActInfo, EIP_PlaySpeed);
					if(gEnv->pMovieSystem)
					{
						gEnv->pMovieSystem->SetPlayingSpeed(m_pSeq, m_playSpeed);
					}
				}
				break;
			}

			case eFE_Initialize:
			{
				pActInfo->pGraph->SetRegularlyUpdated(pActInfo->myID, true);
				m_playSpeed = 1.0f;
				StopSequence(pActInfo);
				m_playingState = PS_NotStarted;
			}
			break;
		};
	};

	virtual void OnMovieEvent(IMovieListener::EMovieEvent event, IAnimSequence* pSequence) {
		// CryLogAlways("CPlaySequence_Node::OnMovieEvent event=%d  seq=%p", event, pSequence);
		if (event == IMovieListener::MOVIE_EVENT_STOP)
		{
			ActivateOutput(&m_actInfo, EOP_Done, true);
			ActivateOutput(&m_actInfo, EOP_Finished, true);
			m_playingState = PS_Finished;
			NotifyEntities();

		}
		else if (event == IMovieListener::MOVIE_EVENT_ABORTED)
		{
			ActivateOutput(&m_actInfo, EOP_Done, true);
			ActivateOutput(&m_actInfo, EOP_Aborted, true);
			m_playingState = PS_Finished;

			NotifyEntities();
		}
	}

protected:

	void PrecacheSequence(SActivationInfo* pActInfo, float startTime = 0.0f)
	{
		IMovieSystem* movieSys = gEnv->pMovieSystem;
		if (movieSys)
		{
			IAnimSequence* pSeq = movieSys->FindSequenceById((uint32)GetPortInt(pActInfo, EIP_SequenceId));
			if (pSeq == NULL)
				pSeq = movieSys->FindSequence(GetPortString(pActInfo, EIP_Sequence));
			if (pSeq)
			{
				pSeq->PrecacheData(startTime);
			}
		}
	}
	
	void StopSequence(SActivationInfo* /*pActInfo*/, bool bUnRegisterOnly=false, bool bAbort=false, bool bLeaveTime=false)
	{
		IMovieSystem *movieSys = gEnv->pMovieSystem;
		if (!movieSys)
			return;

		if (m_pSeq)
		{
			// we remove first to NOT get notified!
			movieSys->RemoveMovieListener(m_pSeq, this);
			if (!bUnRegisterOnly && movieSys->IsPlaying(m_pSeq))
			{
				if (bAbort) // stops sequence and leaves it at current position
					movieSys->AbortSequence(m_pSeq, bLeaveTime);
				else
					movieSys->StopSequence(m_pSeq);
			}
			m_pSeq = 0;
		}
	}

	void StartSequence(SActivationInfo* pActInfo, float curTime = 0.0f, bool bNotifyStarted = true)
	{
		IMovieSystem *movieSys = gEnv->pMovieSystem;
		if (!movieSys)
			return;

		if (m_pSeq)
		{
			movieSys->RemoveMovieListener(m_pSeq, this);
			movieSys->StopSequence(m_pSeq);
			m_pSeq = 0;
			if(m_playingState == PS_Playing)
			{
				m_playingState = PS_Finished;
				NotifyEntities();
			}
		}

		m_pSeq = movieSys->FindSequenceById((uint32)GetPortInt(pActInfo, EIP_SequenceId));
		if (m_pSeq == NULL)
				m_pSeq = movieSys->FindSequence(GetPortString(pActInfo, EIP_Sequence));
		if (m_pSeq)
		{
			m_playingState = PS_Playing;
			
			movieSys->AddMovieListener(m_pSeq, this);
			movieSys->PlaySequence(m_pSeq, NULL, true, false);
			
			m_endTime = m_pSeq->GetTimeRange().end;

			if(curTime < m_pSeq->GetTimeRange().start)
				curTime = m_pSeq->GetTimeRange().start;
			else if(curTime > m_endTime)
				curTime = m_endTime;
			
			movieSys->SetPlayingTime(m_pSeq, curTime);
			movieSys->SetPlayingSpeed(m_pSeq, m_playSpeed);

			// set blend parameters
			IViewSystem* pViewSystem = CCryAction::GetCryAction()->GetIViewSystem();
			if (pViewSystem)
			{
				float blendPosSpeed = GetPortFloat(pActInfo, EIP_BlendPosSpeed);
				float blendRotSpeed = GetPortFloat(pActInfo, EIP_BlendRotSpeed);
				bool performBlendOut = GetPortBool(pActInfo, EIP_PerformBlendOut);
				pViewSystem->SetBlendParams(blendPosSpeed, blendRotSpeed, performBlendOut);
			}

			if (bNotifyStarted)
				ActivateOutput(pActInfo, EOP_Started, true);
			// CryLogAlways("[flow] Animations:PlaySequence: Sequence \"%s\" start", GetPortString(pActInfo, 0));
		} 
		else
		{
			// sequence was not found -> hint 
			GameWarning("[flow] Animations:PlaySequence: Sequence \"%s\" not found", GetPortString(pActInfo, 0).c_str());
			// maybe we should trigger the output, but if sequence is not found this should be an error
		}

		NotifyEntities();
	}

	void NotifyEntityScript(IEntity* pEntity)
	{
		IScriptTable *pEntityScript = pEntity->GetScriptTable();
		if(!pEntityScript)
			return;

		if(m_playingState == PS_Playing && pEntityScript->HaveValue("OnSequenceStart"))
			Script::CallMethod(pEntityScript, "OnSequenceStart");
		else if(m_playingState == PS_Finished && pEntityScript->HaveValue("OnSequenceStop"))
			Script::CallMethod(pEntityScript, "OnSequenceStop");
	}

	void NotifyEntities()
	{
		IMovieSystem *movieSys = gEnv->pMovieSystem;
		if (!movieSys || !m_pSeq)
			return;

		int nodeCount = m_pSeq->GetNodeCount();
		for(int i=0; i<nodeCount; ++i)
		{
			IAnimNode* pNode = m_pSeq->GetNode(i);
			if(pNode)
			{
				IEntity* pEntity = pNode->GetEntity();
				if(pEntity)
					NotifyEntityScript(pEntity);

				if(EntityGUID* guid = pNode->GetEntityGuid())
				{
					EntityId id = gEnv->pEntitySystem->FindEntityByGuid(*guid);
					if(id != 0)
					{
						IEntity* pEntity2 = gEnv->pEntitySystem->GetEntity(id);
						if(pEntity2)
							NotifyEntityScript(pEntity2);
					}
				}
			}
		}
	}


};

REGISTER_FLOW_NODE( "Animations:PlaySequence", CPlaySequence_Node );

